package com.qfedu.entity.chapter2.course11;

public class SnowFlake {
    //因为二进制里第一个 bit 为如果是 1，那么都是负数，但是我们生成的 id 都是正数，所以第一个 bit 统一都是 0。
    //代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个，每毫秒生产的序列号之从0开始递增；
    private long sequence = 0L;
    private long sequenceBits = 12L;
    // 最大值4095，到最大后从0开始
    private long sequenceMask = ~(-1L << sequenceBits);

    //10位机器码看成是“5位dataCenterId+5位workerId”
    //机器ID  2进制5位  32位减掉1位 31个
    private long workerId;
    private long workerIdBits = 5L;
    private long workerIdShift = sequenceBits;

    //机房ID 2进制5位  32位减掉1位 31个
    private long dataCenterId;
    private long dataCenterIdBits = 5L;
    private long dataCenterIdShift = sequenceBits + workerIdBits;

    private long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
    //记录上一个id的产生时间戳，判断是否在 同一毫秒内
    private long lastTimestamp = -1L;

    private SnowFlake(long dataCenterId, long workerId) {
        // 检查机房id和机器id是否超过31 不能小于0
        // 就是5 bit最多只能有31个数字，也就是说机器id最多只能是32以内
        long maxWorkerId = ~(-1L << workerIdBits);
        // 这个是二进制运算，就是5 bit最多只能有31个数字，机房id最多只能是32以内
        long maxDataCenterId = ~(-1L << dataCenterIdBits);
        if ((dataCenterId > maxDataCenterId || dataCenterId < 0) || (workerId > maxWorkerId || workerId < 0)) {
            throw new IllegalArgumentException("dataCenterId/workerId值非法");
        }

        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    //通过SnowFlake生成id的核心算法，通过调用nextId()方法，让当前这台机器上的snowflake算法程序生成一个全局唯一的id
    private synchronized long nextId() {
        //获取计算id时刻的时间戳
        long timestamp = System.currentTimeMillis();
        //保证递增
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时间戳值非法");
        }

        //如果在同一个毫秒内，又发送了一个请求生成一个id，这时把seqence序号给递增1，最多就是4096个（0-4095）
        if (lastTimestamp == timestamp) {
            /*
            通过位运算保证sequence不会超出序列号所能容纳的最大值4095
            如果到达最大值4095此时再增加一个（即sequence + 1），就会使sequence恢复到0,所以可用sequence==0，表示sequence已满。
            */
            sequence = (sequence + 1) & sequenceMask;
            //当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //如果此次生成id的时间戳，与上次的时间戳不同，就已经可以根据时间戳区分id值，sequence从0开始
            sequence = 0L;
        }
        //更新最近一次生成id的时间戳
        lastTimestamp = timestamp;
        /* 最核心的二进制位运算操作，生成id
        1.将41位时间戳左移动22位；
        2.将5位dataCenterId向左移动17位，并将5位workerId向左移动12位；
        3.sequence本来就在最低位，因此不需要移动。
        以下<<和|运算，实际就是将时间戳、机器码和序列号移动到snowflake中相应的位置。
        最后拼接起来成一个64 bit的二进制数字，转换成10进制就是个long型
        */
        /* 时间戳 2^41 - 1   差不多可以用69年
        设置一个时间初始值 1288834974657L（1970-01-01 00:00:00到2010年11月04日01:42:54所经过的毫秒数）
        现在的某一时刻时间戳减去1288834974657L的值，正好在2^41内。
        */
        long twepoch = 1288834974657L;
        return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
    }

    /**
     * 当某一毫秒的时间，产生的id数 超过4095，系统会进入等待，直到下一毫秒，系统继续产生ID
     *
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        /*
        如果当前时刻的时间戳<=上一次生成id的时间戳，就重新生成当前时间。
        即确保当前时刻的时间戳，与上一次的时间戳不会重复。
        */
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * main 测试类
     *
     * @param args
     */
    public static void main(String[] args) {
        SnowFlake worker = new SnowFlake(1, 1);
        for (int i = 0; i < 22; i++) {
            long id = worker.nextId();
            String biStr = Long.toBinaryString(id);
            System.out.println(id + "\t" + biStr);
        }
    }
}